﻿using OA.Infrastructure.Configurations;
using OA.Infrastructure.Enums;
using OA.Infrastructure.Logger;
using OA.Infrastructure.Logger.Abstractions;
using System.Security.Claims;

namespace OA.Infrastructure;

public class AnalysisDbContext<TWrapperContext> : DbContext
    where TWrapperContext : DbContext
{
    private readonly ILoggerOptions _loggerOptions;

    public bool AllowTracking { get; set; } = true;

    public AnalysisDbContext(DbContextOptions<TWrapperContext> options, ILoggerOptions loggerOptions)
        : base(options)
    {
        _loggerOptions = loggerOptions;
    }

    protected ICollection<TrackerEntry> Trackers = new HashSet<TrackerEntry>();

    public DbSet<Department> Department { get; set; } = null!;
    public DbSet<Role> Role { get; set; } = null!;
    public DbSet<User> User { get; set; } = null!;
    public DbSet<Log> Log { get; set; } = null!;
    public DbSet<SystemSettings> SystemSettings { get; set; } = null!;
    public DbSet<ScheduleJob> ScheduleJob { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new UserConfiguration());
        modelBuilder.ApplyConfiguration(new RoleConfiguration());
        modelBuilder.ApplyConfiguration(new DepartmentConfiguration());
        modelBuilder.ApplyConfiguration(new LogConfiguration());
        modelBuilder.ApplyConfiguration(new SystemSettingsConfiguration());
        modelBuilder.ApplyConfiguration(new ScheduleJobConfiguration());

        base.OnModelCreating(modelBuilder);
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        var result = 0;
        try
        {
            var existTracker = TryAnalyzeChangeTracker();

            result = await base.SaveChangesAsync(cancellationToken);

            if (!existTracker)
            {
                return result;
            }

            ReAnalyzeTemporaryProperties();

            var account = App.HttpContext is null
                ? "System"
                : App.HttpContext!.User.Claims.FirstOrDefault(n => n.Type is "name" or ClaimTypes.Name)?.Value ?? "admin";

            var updatedBy = User.First(n => n.Account == account);

            foreach (var tracker in Trackers)
            {
                foreach (var (tableName, displayName) in tracker.Tables)
                {
                    var possibleFormId = 0;

                    if (tracker.OperationCategory == OperationCategory.Update && tracker.CurrentValues[tableName] == null)
                    {
                        possibleFormId = (int)tracker.KeyValues[tableName]["Id"]!;
                    }

                    if (tracker.KeyValues.Count == 2)
                    {
                        possibleFormId = (int)tracker.KeyValues.First(n => n.Key != tableName).Value.First().Value!;
                    }

                    if (tracker.OperationCategory != OperationCategory.Delete &&
                        tracker.CurrentValues[tableName] != null &&
                        tracker.CurrentValues[tableName]!.Count != 0)
                    {
                        foreach (var (prop, value) in tracker.CurrentValues[tableName]!)
                        {
                            var formId = tracker.KeyValues.Count > 1 ? possibleFormId : (int)tracker.KeyValues[tableName]["Id"]!;

                            var old = tracker.OriginalValues.Count == 0 ? "null" : tracker.OriginalValues[tableName]![prop] == null ? "null" : tracker.OriginalValues[tableName]![prop]!.ToString()!;
                            var @new = value == null ? "null" : value.ToString()!;

                            var realOldValue = this.GetFieldCorrespondingValue(tableName, formId, prop, old);
                            var realNewValue = this.GetFieldCorrespondingValue(tableName, formId, prop, @new);

                            var log = new Log
                            {
                                CreatedDate = DateTime.UtcNow,
                                Operator = updatedBy,
                                Operation = tracker.OperationCategory,
                                FormId = formId,
                                Field = tracker.Fields[tableName][prop] == null ? "null" : tracker.Fields[tableName][prop]!.ToString()!,
                                FormType = displayName,
                                Old = !string.IsNullOrEmpty(realOldValue) ? realOldValue : "null",
                                New = !string.IsNullOrEmpty(realNewValue) ? realNewValue : "null",
                            };

                            Log.Add(log);
                        }
                    }
                    else if (tracker.OperationCategory == OperationCategory.Delete &&
                        tracker.OriginalValues[tableName] != null &&
                        tracker.OriginalValues[tableName]!.Count != 0)
                    {
                        foreach (var (prop, value) in tracker.OriginalValues[tableName]!)
                        {
                            var formId = tracker.KeyValues.Count > 1 ? possibleFormId : (int)tracker.KeyValues[tableName]["Id"]!;

                            var realOldValue = value == null ? null : this.GetFieldCorrespondingValue(tableName, formId, prop, value.ToString()!);

                            var log = new Log
                            {
                                Operator = updatedBy,
                                Operation = tracker.OperationCategory,
                                FormId = formId,
                                Field = tracker.Fields[tableName][prop] == null ? "null" : tracker.Fields[tableName][prop]!.ToString()!,
                                FormType = displayName,
                                Old = !string.IsNullOrEmpty(realOldValue) ? realOldValue : "null",
                                New = "null",
                            };

                            Log.Add(log);
                        }
                    }

                    possibleFormId = 0;
                    Trackers.Remove(tracker);
                }
            }

            await SaveChangesAsync(cancellationToken);

            return result;
        }
        catch
        {
            return result;
        }
    }

    protected bool TryAnalyzeChangeTracker()
    {
        if (!AllowTracking)
        {
            return false;
        }

        var trackerCount = 0;
        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.State is EntityState.Detached or EntityState.Unchanged)
            {
                continue;
            }

            if (_loggerOptions.EntityFilters.Any(n => !n.Invoke(entry)))
            {
                continue;
            }

            Trackers.Add(new TrackerEntry(entry, _loggerOptions));
            trackerCount++;
        }

        return trackerCount > 0;
    }

    protected void ReAnalyzeTemporaryProperties()
    {
        if (Trackers is not null && Trackers.Count != 0)
        {
            foreach (var tracker in Trackers)
            {
                if (tracker.TemporaryProperties is null)
                {
                    continue;
                }

                foreach (var (tableName, properties) in tracker.TemporaryProperties)
                {
                    if (properties is not null && properties.Count != 0)
                    {
                        foreach (var temporaryProperty in properties)
                        {
                            var colName = temporaryProperty.GetColumnName();

                            if (temporaryProperty.Metadata.IsPrimaryKey())
                            {
                                tracker.KeyValues[tableName][colName] = temporaryProperty.CurrentValue;
                            }

                            switch (tracker.OperationCategory)
                            {
                                case OperationCategory.Query:
                                    break;
                                case OperationCategory.Add:
                                    tracker.CurrentValues[tableName]![colName] = temporaryProperty.CurrentValue;
                                    break;
                                case OperationCategory.Delete:
                                    tracker.OriginalValues[tableName]![colName] = temporaryProperty.CurrentValue;
                                    break;
                                case OperationCategory.Update:
                                    tracker.OriginalValues[tableName]![colName] = temporaryProperty.OriginalValue;
                                    tracker.CurrentValues[tableName]![colName] = temporaryProperty.CurrentValue;
                                    break;
                                default:
                                    break;
                            }
                        }

                        tracker.TemporaryProperties[tableName] = null;
                    }
                }
            }
        }
    }
}